package com.xxx.utils;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;

/**
 * stringRedis操作工具类
 */
public class RedisUtil {

    /************************************ 单key 相关操作 start ***************************************/
    /**
     * 删除指定key
     *
     * @param key
     */
    public static void del(String key) {
        if (StringUtils.isEmpty(key)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.delete(key);
    }

    /**
     * 批量删除key
     *
     * @param keys
     */
    public static void del(Collection<String> keys) {
        if (CollectionUtils.isEmpty(keys)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.delete(keys);
    }

    /**
     * 判断key是否存在
     */
    public static boolean exists(String key) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 设置key过期时间
     *
     * @param key
     * @param expiredTime 过期时间，单位：秒
     * @return
     */
    public static boolean setExpire(String key, long expiredTime) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.expire(key, expiredTime, TimeUnit.SECONDS);
    }
    
    /**
     * 自增或自减
     *
     * @param key
     */
    public static void incr(String key) {
        if (StringUtils.isEmpty(key)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForValue().increment(key, 1);
    }

    /**
     * 自增或自减
     *
     * @param key
     * @param delta 正数为增加，负数为减少
     */
    public static void incr(String key, long delta) {
        if (StringUtils.isEmpty(key) || delta == 0) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForValue().increment(key, delta);
    }
    /************************************ 单key 相关操作 end ***************************************/

    /************************************ string 相关操作 start ***************************************/
    /**
     * 设置key和value，并设置过期时间
     *
     * @param key
     * @param value
     * @param expiredSeconds 单位：秒
     */
    public static void set(String key, String value, long expiredSeconds) {
        if (StringUtils.isEmpty(key) || expiredSeconds <= 0) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForValue().set(key, value, expiredSeconds, TimeUnit.SECONDS);
    }
    
    /**
     * 如果键不存在则新增，存在则不改变已经有的值
     *
     * @param key
     * @param value
     * @param expiredSeconds 单位：秒
     */
    public static void setIfAbsent(String key, String value, long expiredSeconds) {
        if (StringUtils.isEmpty(key) || expiredSeconds <= 0) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForValue().setIfAbsent(key, value, expiredSeconds, TimeUnit.SECONDS);
    }
    
    /**
     * 根据单个key获取value
     *
     * @param key
     * @return
     */
    public static String get(String key) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 根据key列表，批量获取value
     *
     * @param keyList
     * @return
     */
    public static List<String> mGet(List<String> keyList) {
        if (CollectionUtils.isEmpty(keyList)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForValue().multiGet(keyList);
    }
    /************************************ string 相关操作 end ***************************************/

    /************************************ hash 相关操作 start ***************************************/
    /**
     * hash set：通过key给field设置指定的值,如果key不存在,则先创建
     *
     * @param key
     * @param field 字段
     * @param value
     */
    public static void hSet(String key, String field, Object value) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * hash field value increment
     *
     * @param key   hash key
     * @param field field
     */
    public static void hIncrement(String key, String field) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForHash().increment(key, field, 1L);
    }

    /**
     * hash field value increment
     *
     * @param key   hash key
     * @param field field
     * @param delta 增量
     */
    public static void hIncrement(String key, String field, long delta) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * hash mset：同时设置同一个key的hash的多个field和value
     *
     * @param key
     * @param map
     */
    public static void hMset(String key, Map<String, Object> map) {
        if (StringUtils.isEmpty(key) || MapUtils.isEmpty(map)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * hash get：通过key 和 field 获取指定的 value
     *
     * @param key
     * @param field
     * @return 没有返回null
     */
    public static Object hGet(String key, String field) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForHash().get(key, field);
    }

    /**
     * hash mget：通过key 和 field 获取指定的 value
     *
     * @param key
     * @param fields
     * @return 按照fields列表的顺序返回对应的值
     */
    public static List<Object> hMget(String key, List<Object> fields) {
        if (StringUtils.isEmpty(key) || CollectionUtils.isEmpty(fields)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForHash().multiGet(key, fields);
    }

    /**
     * hash scan：扫描指定key下的记录
     *
     * @param key
     * @param scanOptions
     */
    public static Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions scanOptions) {
        if (StringUtils.isEmpty(key) || scanOptions==null) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForHash().scan(key, scanOptions);
    }

    /**
     * hash delete：根据指定key删除指定field
     *
     * @param key
     * @param field 字段
     */
    public static void hDel(String key, String field) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(field)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForHash().delete(key, field);
    }
    /************************************ hash 相关操作 end ***************************************/

    /************************************ set 相关操作 start ***************************************/
    /**
     * set sadd: 通过key向指定的set中添加value
     *
     * @param key
     * @param values 可以是一个String 也可以是一个String数组
     * @return 添加成功的个数
     */
    public static Long sAdd(String key, String... values) {
        if (StringUtils.isEmpty(key)) {
            return 0L;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForSet().add(key, values);
    }

    /**
     * set rem: 通过key删除set中对应的value值
     *
     * @param key
     * @param values 可以是一个String 也可以是一个String数组
     * @return 删除的个数
     */
    public static Long sRemove(String key, Object... values) {
        if (StringUtils.isEmpty(key)) {
            return 0L;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForSet().remove(key, values);
    }

    /**
     * set pop: 通过key随机删除一个set中的value并返回该值
     *
     * @param key
     * @return
     */
    public static String sPop(String key) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForSet().pop(key);
    }

    /**
     * set card: 通过key获取set中value的个数
     *
     * @param key
     * @return
     */
    public static Long sSize(String key) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForSet().size(key);
    }

    /**
     * set members: 通过key获取set中所有的value
     *
     * @param key
     * @return
     */
    public static Set<String> sMembers(String key) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * set isMember: 通过key判断value是否是set中的元素
     *
     * @param key
     * @param member
     * @return
     */
    public static Boolean sIsMember(String key, Object member) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForSet().isMember(key, member);
    }
    /************************************ set 相关操作 end ***************************************/

    /************************************ zset 相关操作 start ***************************************/
    /**
     * zset add：通过key向zset中添加value,score,其中score就是用来排序的 如果该value已经存在则根据score更新元素
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public static void zAdd(String key, String value, double score) {
        if (StringUtils.isEmpty(key)) {
            return;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        stringRedisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * zset rem: 通过key删除在zset中指定的value
     *
     * @param key
     * @param values 可以使一个object, 也可以是一个object数组
     * @return
     */
    public static Long zRem(String key, Object... values) {
        if (StringUtils.isEmpty(key)) {
            return 0L;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * zset count: 返回指定score区间内zset中value的数量
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public static Long zCount(String key, double min, double max) {
        if (StringUtils.isEmpty(key)) {
            return 0L;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForZSet().count(key, min, max);
    }

    /**
     * zset zCard: 通过key返回zset中的value个数
     *
     * @param key
     * @return
     */
    public static Long zCard(String key) {
        if (StringUtils.isEmpty(key)) {
            return 0L;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForZSet().zCard(key);
    }

    /**
     * zset score: 通过key获取zset中value的score值
     *
     * @param key
     * @param value
     * @return
     */
    public static double zScore(String key, Object value) {
        if (StringUtils.isEmpty(key)) {
            return 0L;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForZSet().score(key, value);
    }

    /**
     * zset range: 通过key将获取score从start到end中zset的value 当start为0 end为-1时返回全部
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public static Set<String> zRange(String key, long start, long end) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * zset zrangeByScore：通过key返回指定score内zset中的value
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public static Set<String> zRangeByScore(String key, double min, double max) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * zset zrangeWithScore：返回有序集key中，指定区间内的成员和它的score值。其中成员的位置按score值递增(从小到大)来排列。
     *
     * @param <V>
     * @param key
     * @param start
     * @param end
     * @return
     */
    public static <V> Map<String, Double> zRangeWithScoresToMap(String key, long start, long end) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        Set<TypedTuple<String>> res = stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);
        Map<String, Double> map = new HashMap<>();
        for (TypedTuple<String> t : res) {
            map.put(t.getValue(), t.getScore());
        }
        return map;
    }
    /************************************ zset 相关操作end  ***************************************/

    /************************************ bitmap 相关操作 start ***************************************/
    /**
	 * setbit操作：设置或清除指定偏移量上的位(bit)。 
	 * 当 key不存在时，自动生成一个新的字符串值。 字符串会进行伸展(grown)以确保它可以将 value保存在指定的偏移量上。当字符串值进行伸展时，空白位置以 0 填充。
	 * @param key
	 * @param offset：偏移量
	 * @param value：只能是0或1
	 * @return true：该位置原来为1，false：该位置原来为0
	 */
	public static boolean setbit(String key, long offset, boolean value) {
        if (StringUtils.isEmpty(key) || offset <= 0) {
            return false;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
//        return (boolean) stringRedisTemplate.execute((RedisCallback<Boolean>) con -> con.setBit(key.getBytes(), offset, value));
        return stringRedisTemplate.opsForValue().setBit(key, offset, value);
	}
    
	/**
	 * getbit操作：获取指定偏移量上的位(bit)。 如果key不存在或者offset不存在的，则返回 0。
	 * @param key
	 * @param offset：偏移量
	 * @return true：该位置为1，false：该位置为0
	 */
    public static boolean getbit(String key, long offset) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
//        return (boolean) stringRedisTemplate.execute((RedisCallback<Boolean>) con -> con.getBit(key.getBytes(), offset));
        return stringRedisTemplate.opsForValue().getBit(key, offset);
    }
    
    /**
	 * bitcount操作：获取value为1的个数。
	 * @param key
	 * @return value为1的个数
	 */
    public static long bitcount(String key) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return (long) stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
    }
    
    /**
	 * bitcount操作：获取指定区间值为1的个数。 说明： BITCOUNT key [start end] 从左向右从0开始，从右向左从-1开始，注意start和end是字节 BITCOUNT
	 * 例如：testkey 0 0 表示从索引0个字节到索引0个字节，就是第一个字节的统计 BITCOUNT testkey 0 -1 等同于BITCOUNT testkey
	 * @param key
	 * @param start
	 * @param end
	 * @return 值为1的个数
	 */
    public static long bitcount(String key, long start, long end) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }

        StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return (long) stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
    }
    
    /**
	 * bitpos操作：返回字符串里面第一个被设置为1或者0的bit位。 如果我们在空字符串或者0字节的字符串里面查找bit为1的内容，那么结果将返回-1。
	 * 如果我们在字符串里面查找bit为0而且字符串只包含1的值时，将返回字符串最右边的第一个空位。 如果有一个字符串是三个字节的值为0xff的字符串，那么命令BITPOS key 0将会返回24，因为0-23位都是1。
	 * @param key
	 * @param value
	 * @return 第一个被设置为1或者0的bit位。 时间复杂度：O(N)
	 */
	public static long bitpos(String key, boolean value) {
		if (StringUtils.isEmpty(key)) {
            return 0;
        }
		
		StringRedisTemplate stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        return (long) stringRedisTemplate.execute((RedisCallback<Long>) con -> con.bitPos(key.getBytes(), value));
	}

	/************************************ bitmap 相关操作 end ***************************************/
	
}
